﻿using System;
using System.Linq;
using Common.Reflection;

namespace Common.Exceptional.Routing
{
    public class ExceptionPipeline : IExceptionPipeline
    {
        public ExceptionMessage Push(Exception ex)
        {
            var exMessage = new ExceptionMessage(ex);
            return Push(exMessage);
        }

        public ExceptionMessage Push(ExceptionMessage exMessage)
        {
            var exceptionType = exMessage.RawExceptionType;

            var routes = ExceptionConfiguration.Current.RouteInfos
                .Where(i => i.ExceptionType.IsAssignableFrom(exceptionType)).ToList();

            while (exceptionType != null)
            {
                var route = routes.FirstOrDefault(i => i.ExceptionType == exceptionType);
                if (route == null || !typeof(Exception).IsAssignableFrom(exceptionType))
                {
                    exceptionType = exceptionType.BaseType;
                    continue;
                }

                foreach (var handlerInfo in route.ExceptionHandlers.OrderBy(i => i.HandleOrder))
                {
                    if (!handlerInfo.HandlerChannel.IsNullOrWhiteSpace())
                        if (exMessage.Channel != handlerInfo.HandlerChannel)
                            continue;

                    var handlerType = handlerInfo.HandlerType;
                    var handler = Activator.CreateInstance(handlerType);
                    if (exMessage.ShouldContinueRouting && !exMessage.ProcessedHandlers.Contains(handlerType))
                    {
                        try
                        {
                            var method = handlerType.GetImplementOfInterfaceMethod(
                                typeof(IExceptionHandler<>).MakeGenericType(exceptionType).GetMethods().First());
                            method.Invoke(handler, new object[] { exMessage });
                        }
                        finally
                        {
                            exMessage.ProcessedHandlers.Add(handlerType);
                        }
                    }
                }
                exceptionType = exceptionType.BaseType;
            }

            return exMessage;
        }
    }
}